package com.capsulode.excel.sheetreader;

import com.capsulode.excel.BatchConsumer;
import com.capsulode.excel.domain.ExcelImportResult;
import com.capsulode.excel.exception.*;
import com.capsulode.excel.header.Header;
import com.capsulode.excel.util.ExcelUtil;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;

import java.util.*;

public abstract class AbstractSheetReader<R> implements SheetReader<R> {

    private final Map<String, Header<R>> headers = new TreeMap<>();
    private final Map<Integer, Header<R>> columns = new TreeMap<>();

    private final Collection<String> ignoredColumns = new ArrayList<>();
    private final Map<Integer, RowFailure> failures = new TreeMap<>();

    private int chunkSize = 100;
    private int dataBeginIndex = 0;

    @Override
    public AbstractSheetReader<R> batchSize(int size) {
        if (size <= 0) {
            throw new IllegalArgumentException("Batch size must be positive。");
        }
        chunkSize = size;
        return this;
    }

    @Override
    public SheetReader<R> from(int rowIndex) {
        this.dataBeginIndex = rowIndex;
        return this;
    }

    public AbstractSheetReader<R> header(Header<R> header) {
        headers.put(header.getName(), header);
        return this;
    }

    @Override
    public ExcelImportResult consume(Sheet sheet, BatchConsumer<R> chunk) {
        if (dataBeginIndex <= 0) {
            int headerRowIndex = findHeaderRow(sheet);
            dataBeginIndex = headerRowIndex + 1;
            _readHeaders(sheet, headerRowIndex);
        }else{
            int i=0;
            for(Header<R> header : headers.values()){
                columns.put(header.getIndex(),header);
            }
        }

        int lines = 0;
        List<R> rows = new ArrayList<>(chunkSize);
        for (int i = dataBeginIndex; i <= sheet.getLastRowNum(); i++) {
            Row row = sheet.getRow(i);
            R wrapper = _readRow(row);
            if (wrapper != null) {
                rows.add(wrapper);

                if (rows.size() == chunkSize || i == sheet.getLastRowNum()) {
                    chunk.batch(rows);
                    lines += rows.size();
                    rows.clear();
                }
            }

        }
        return new ExcelImportResult(lines, ignoredColumns, failures.values());
    }

    protected abstract R newRow(int rowNum) throws RowWrappingFailedException;

    private int findHeaderRow(Sheet sheet) throws HeadersMissMatchException {
        if (headers.isEmpty()) {
            throw new IllegalStateException("Headers undefined yet!");
        }
        int utilRowIndex = Math.min(sheet.getFirstRowNum() + 16, sheet.getLastRowNum() + 1);
        int mostLikely = 0, mostLikelyRowIndex = 0;
        for (int i = sheet.getFirstRowNum(); i < utilRowIndex; i++) {
            Iterator<Cell> iterator = sheet.getRow(i).iterator();
            int likely = 0;
            while (iterator.hasNext()) {
                Cell cell = iterator.next();
                if (CellType.STRING.equals(cell.getCellTypeEnum()) && headers.containsKey(cell.getStringCellValue())) {
                    likely++;
                }
            }
            if (likely > mostLikely) {
                mostLikely = likely;
                mostLikelyRowIndex = i;
            }
        }
        if (mostLikely < headers.size() * 0.7 + 1) {
            throw new HeadersMissMatchException("错误的Excel模板：表头不匹配，支持的表头为：" + headers.keySet());
        }
        return mostLikelyRowIndex;
    }

    private void _readHeaders(Sheet sheet, int headerRowIndex) {
        for (Cell cell : sheet.getRow(headerRowIndex)) {
            String headerName = cell.getStringCellValue();
            Header<R> header = headers.get(headerName);
            if (header == null) {
                ignoredColumns.add(headerName);
                continue;
            }
            int headerIndex = cell.getColumnIndex();
            header.setIndex(headerIndex);//important
            columns.put(headerIndex, header);
        }
    }

    private R _readRow(Row row) {
        R data;
        try {
            data = newRow(row.getRowNum() + 1);
        } catch (RowWrappingFailedException ex) {
            throw new IllegalStateException("Cannot process import", ex);
        }
        boolean hasError = false;

        for (Map.Entry<Integer, Header<R>> entry : columns.entrySet()) {
            Cell cell = row.getCell(entry.getKey());
            Header<R> header = entry.getValue();
            try {
                header.read(cell, data);
            } catch (ConsumeCellFailedException | ReadingProcessFailedException | UnsupportedConversionException | ValidationFailedException ex) {
                _addFailure(row, new CellFailure(row.getRowNum(), header.getIndex(), header.getName(), ex.getMessage()));
                hasError = true;
            }
        }
        return hasError ? null : data;
    }


    private void _addFailure(Row row, CellFailure failure) {
        RowFailure rowFailure = failures.computeIfAbsent(row.getRowNum(),
                k -> new RowFailure(k, ExcelUtil.raw(row)));
        rowFailure.addFailure(failure);
    }
}
